package com.heima.wemedia.service.impl;

import com.alibaba.fastjson.JSON;
import com.heima.apis.article.IArticleClient;
import com.heima.audit.baidu.BaiduImageScan;
import com.heima.audit.baidu.BaiduTextScan;
import com.heima.audit.tess4j.Tess4jClient;
import com.heima.common.exception.CustomException;
import com.heima.common.redis.CacheService;
import com.heima.file.service.FileStorageService;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.wemedia.pojos.WmChannel;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.model.wemedia.pojos.WmSensitive;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.common.SensitiveWordUtil;
import com.heima.wemedia.service.*;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class WmNewsAuditServiceImpl implements WmNewsAuditService {


    @Autowired
    private BaiduTextScan baiduTextScan;

    @Autowired
    private FileStorageService fileStorageService;

    @Autowired
    private BaiduImageScan baiduImageScan;

    @Autowired
    private IArticleClient articleClient;

    @Autowired
    private WmChannelService wmChannelService;

    @Autowired
    private WmUserService wmUserService;

    @Autowired
    private Tess4jClient tess4jClient;

    @Autowired
    private WmNewsTaskService wmNewsTaskService;
    @Autowired
    private KafkaTemplate kafkaTemplate;

    /**
     * 自媒体文章自动审核
     * @param wmNews
     * @param contentImageUrlList
     * @return
     */
    @GlobalTransactional
    @Async("taskExecutor")
    public void auditWmNews(WmNews wmNews, List<String> contentImageUrlList) {
        kafkaTemplate.send("asd","asd");
        try {
            TimeUnit.SECONDS.sleep(10); //模拟文章审核耗时超过10秒
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        //1. 准备：提取全部文本和图片
        //1.1 提取全部文本（标题、标签、内容文本）
        String text = prepareAllText(wmNews);

        //1.2 提取全部图片的地址列表（封面图片地址列表+内容图片地址列表） - 需要地址去重
        List<String> imageUrlList = prepareAllImage(wmNews, contentImageUrlList);

        //1.3 识别出所有图片中的文本
        text = ocrText(text, imageUrlList);

        //2. 第1阶段审核：DFA文本审核
        boolean flag = this.dfaTextScan(wmNews, text);
        if(!flag){
            return;
        }

        //3. 第2阶段审核：百度云文本审核
        flag = this.baiduTextScan(wmNews, text);
        if(!flag){
            return;
        }

        //4. 第3阶段审核：百度云图片审核
        flag = this.baiduImageScan(wmNews,imageUrlList);
        if(!flag){
            return;
        }

        //5. 审核通过后决定文章是否发布
        //5.1 获取系统当前时间和自媒体文章发布时间
        //long currentTime = System.currentTimeMillis();
        long currentTime = DateTime.now().getMillis(); //系统当前时间
        long publishTime = wmNews.getPublishTime().getTime(); //自媒体文章发布时间

        //5.2 发布时间未到（自媒体文章发布时间>系统当前时间），则修改自媒体文章状态为审核通过待发布状态（8）
        if(publishTime>currentTime){
            String reason = "审核通过，发布时间未到";
            this.updateWmNews(wmNews,WmNews.Status.SUCCESS.getCode(), reason);

            //将自媒体文章封装为任务添加到DB任务表和Cache队列中
            wmNewsTaskService.addTask(wmNews);
        } else {
            //5.3 发布时间已到（系统当前时间>=自媒体文章发布时间），则创建或修改APP文章数据且修改自媒体文章状态为已发布状态（9）
            doArticlePublish(wmNews);
        }
    }

    /**
     * 识别出所有图片中的文本
     * @param text
     * @param imageUrlList
     * @return
     */
    private String ocrText(String text, List<String> imageUrlList) {
        StringBuffer ocrText = new StringBuffer();
        if(imageUrlList.size()>0){
            for (String url : imageUrlList) {
                byte[] bytes = fileStorageService.downLoadFile(url);

                try {
                    //从byte[]转换为butteredImage
                    ByteArrayInputStream in = new ByteArrayInputStream(bytes);
                    BufferedImage imageFile = ImageIO.read(in);
                    //识别图片的文字
                    String result = tess4jClient.doOCR(imageFile);
                    if(StringUtils.isNotBlank(result)){
                        ocrText.append(result);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        text = ocrText.append(text).toString();
        return text;
    }

    @Autowired
    private WmSensitiveService wmSensitiveService;

    @Autowired
    private CacheService cacheService;

    /**
     * DFA算法文本审核
     * @param wmNews
     * @param text
     * @return
     */
    private boolean dfaTextScan(WmNews wmNews, String text) {

        String redisKey = "sensitive:word:list";
        //1. 从Redis缓存中查询敏感词列表
        List<String> sensitiveWordList = cacheService.lRange(redisKey, 0, -1);

        //2. 没有查询到就从表中查询敏感词列表然后更新到Redis里
        if(sensitiveWordList.isEmpty()){
            //查询敏感词列表 select sensitives from wm_sensitve
            List<WmSensitive> wmSensitiveList = wmSensitiveService.lambdaQuery().select(WmSensitive::getSensitives).list();
            sensitiveWordList = wmSensitiveList.stream().map(WmSensitive::getSensitives).collect(Collectors.toList());

            //将敏感词列表更新到Redis
            cacheService.lLeftPushAll(redisKey,sensitiveWordList);
        }

        //2. 初始化DFA词库
        SensitiveWordUtil.initMap(sensitiveWordList);

        //3. 对文本进行检测得到map
        Map<String, Integer> resultMap = SensitiveWordUtil.matchWords(text);

        //4. 对map结果进行处理
        if(resultMap.size()>0){
            String reason = "DFA算法文本审核结果：不合规";
            this.updateWmNews(wmNews, WmNews.Status.FAIL.getCode(), reason);
            log.error("[文章自动审核]{}，文章ID：{}", reason, wmNews.getId());
            return false;
        }

        return true;
    }

    /**
     * 处理文章发布
     * @param wmNews
     */
    public void doArticlePublish(WmNews wmNews) {
        //1 构建Dto数据
        ArticleDto dto = new ArticleDto();
        dto.setId(wmNews.getArticleId()); //APP文章id
        dto.setTitle(wmNews.getTitle());//文章标题
        dto.setImages(wmNews.getImages());//封面图片地址
        dto.setLabels(wmNews.getLabels());//文章标签
        dto.setLayout(wmNews.getType());//封面布局方式
        dto.setPublishTime(wmNews.getPublishTime());//文章发布时间
        dto.setContent(wmNews.getContent());//文章内容
        if(dto.getId()==null){
            dto.setCreatedTime(new Date());//创建时间
        }

        //根据频道ID查询频道
        WmChannel wmChannel = wmChannelService.getById(wmNews.getChannelId());
        if(wmChannel!=null){
            dto.setChannelId(wmChannel.getId());//频道id
            dto.setChannelName(wmChannel.getName());//频道名称
        }

        //根据自媒体用户ID查询自媒体用户
        WmUser wmUser = wmUserService.getById(wmNews.getUserId());
        if(wmUser!=null){
            dto.setAuthorId(wmUser.getApAuthorId().longValue()); //作者id
            dto.setAuthorName(wmUser.getName());//作者名
        }

        //设置行为数量的值，默认都为0
        dto.setLikes(0);//阅读数量
        dto.setCollection(0);//收藏数量
        dto.setComment(0);//评论数量
        dto.setViews(0);//阅读数量

        //2 调用Feign接口创建或修改APP文章
        ResponseResult responseResult = articleClient.saveOrUpdateArticle(dto);
        if(responseResult.getCode()!=200){
            throw new CustomException(AppHttpCodeEnum.FEIGN_INVOKE_FAIL);
        }

        //3 调用Feign的结果如果有问题，可以抛出自定义异常
        Long articleId = Long.valueOf(String.valueOf(responseResult.getData()));

        //4 获取结果里的articleId值，设置到wmNews中
        wmNews.setArticleId(articleId);

        //5 更新自媒体文章（状态变更为已发布且修改aritcle_id的值）
        String reason = "审核通过已发布";
        this.updateWmNews(wmNews,WmNews.Status.PUBLISHED.getCode(), reason);
    }


    /**
     * 百度云图片审核
     * @param wmNews
     * @param imageUrlList
     * @return
     */
    private boolean baiduImageScan(WmNews wmNews, List<String> imageUrlList){
        if(imageUrlList.size()>0){
            for (String url : imageUrlList) {
                //1 根据图片的URL地址下载图片的字节数组
                byte[] bytes = fileStorageService.downLoadFile(url);

                //2 使用baiduImageScan的审核方法进行审核，得到type响应值
                Integer typeForImage = baiduImageScan.imageScan(bytes);

                //3 处理type响应结果
                //如果type的值是2，说明不合规，则修改文章状态为审核失败状态（2），后续审核流程终止
                if(typeForImage==2){
                    String reason = "百度云图片审核结果：不合规";
                    this.updateWmNews(wmNews,WmNews.Status.FAIL.getCode(), reason);
                    log.error("[文章自动审核]{}，文章ID：{}", reason, wmNews.getId());
                    return false;
                } else if(typeForImage==3){
                    //如果type的值是3，说明不确定，则修改文章状态为待人工审核状态（3），后续审核流程终止
                    String reason = "百度云图片审核结果：不确定";
                    this.updateWmNews(wmNews,WmNews.Status.ADMIN_AUTH.getCode(), reason);
                    log.error("[文章自动审核]{}，文章ID：{}", reason, wmNews.getId());
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 百度云文本审核
     * @param wmNews
     * @param text
     * @return
     */
    private boolean baiduTextScan(WmNews wmNews, String text) {
        //1 使用baiduTextScan的审核方法进行审核，得到type响应值
        Integer typeForText = baiduTextScan.textScan(text);

        //2 处理type响应结果
        //如果type的值是2，说明不合规，则修改文章状态为审核失败状态（2），后续审核流程终止
        if(typeForText==2){

            String reason = "百度云文本审核结果：不合规";
            this.updateWmNews(wmNews,WmNews.Status.FAIL.getCode(), reason);
            log.error("[文章自动审核]{}，文章ID：{}", reason, wmNews.getId());
            return false;
        } else if(typeForText==3){
            //如果type的值是3，说明不确定，则修改文章状态为待人工审核状态（3），后续审核流程终止
            String reason = "百度云文本审核结果：不确定";
            this.updateWmNews(wmNews,WmNews.Status.ADMIN_AUTH.getCode(), reason);
            log.error("[文章自动审核]{}，文章ID：{}", reason, wmNews.getId());
            return false;
        }

        return true;
    }

    /**
     * 提取全部图片的地址列表（封面图片地址列表+内容图片地址列表） - 需要地址去重
     * @param wmNews
     * @param contentImageUrlList
     * @return
     */
    private List<String> prepareAllImage(WmNews wmNews, List<String> contentImageUrlList) {
        if(StringUtils.isNotBlank(wmNews.getImages())){
            String[] coverImageArr = wmNews.getImages().split(",");
            List<String> coverImageUrlList = Arrays.asList(coverImageArr);  //封面图片地址列表

            contentImageUrlList.addAll(coverImageUrlList);
        }

        //Set<String> imageUrlList = new HashSet<>(contentImageUrlList);
        List<String> imageUrlList = contentImageUrlList.stream().distinct().collect(Collectors.toList());//待审核的图片地址
        return imageUrlList;
    }

    /**
     * 提取全部文本（标题、标签、内容文本）
     * @param wmNews
     * @return
     */
    private String prepareAllText(WmNews wmNews) {
        StringBuffer allText = new StringBuffer();
        allText.append(wmNews.getTitle()); //拼接标题
        allText.append(wmNews.getLabels());//拼接标签
        List<Map> mapList = JSON.parseArray(wmNews.getContent(), Map.class);
        if(mapList!=null && mapList.size()>0){
            for (Map<String,String> map : mapList) {
                String type = map.get("type");
                if("text".equals(type)){
                    String contentText = map.get("value");
                    allText.append(contentText);//拼接内容文本
                }
            }
        }
        String text = allText.toString(); //待审核的文本
        return text;
    }

    @Autowired
    private WmNewsService wmNewsService;

    /**
     * 公共方法：更新文章状态
     * @param wmNews 文章对象
     * @param status 状态
     * @param reason 原因
     */
    private void updateWmNews(WmNews wmNews,int status,String reason){
        wmNews.setStatus(status);
        wmNews.setReason(reason);
        wmNewsService.updateById(wmNews);
    }

}
